home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2006 December / PCWDEC06.iso / Software / Freeware / Gmail Manager 0.5.1 / gmanager051.xpi / components / gmServiceGmail.js < prev    next >
Encoding:
Text File  |  2006-08-28  |  19.8 KB  |  666 lines

  1. // Gmail Manager
  2. // By Todd Long <longfocus@gmail.com>
  3. // http://www.longfocus.com/firefox/gmanager/
  4.  
  5. const GM_CC = Components.classes;
  6. const GM_CI = Components.interfaces;
  7.  
  8. const GM_NOTIFY_ACCOUNT_STATE_CONNECTING = "gm-account-state-connecting";
  9. const GM_NOTIFY_ACCOUNT_STATE_LOGGED_OUT = "gm-account-state-logged-out";
  10. const GM_NOTIFY_ACCOUNT_STATE_LOGGED_OUT_NO_PASSWORD = "gm-account-state-logged-out-no-password";
  11. const GM_NOTIFY_ACCOUNT_STATE_LOGGED_IN_NO_MAIL = "gm-account-state-logged-in-no-mail";
  12. const GM_NOTIFY_ACCOUNT_STATE_LOGGED_IN_NEW_MAIL = "gm-account-state-logged-in-new-mail";
  13. const GM_NOTIFY_ACCOUNT_STATE_ERROR_PASSWORD = "gm-account-state-error-password";
  14. const GM_NOTIFY_ACCOUNT_STATE_ERROR_CAPTCHA = "gm-account-state-error-captcha";
  15. const GM_NOTIFY_ACCOUNT_STATE_ERROR_NETWORK = "gm-account-state-error-network";
  16. const GM_NOTIFY_ACCOUNT_STATE_ERROR_SERVER = "gm-account-state-error-server";
  17. const GM_NOTIFY_ACCOUNT_STATE_ERROR_TIMEOUT = "gm-account-state-error-timeout";
  18.  
  19. function gmServiceGmail()
  20. {
  21.   this._console = GM_CC['@mozilla.org/consoleservice;1'].getService(GM_CI.nsIConsoleService);
  22.   this._observer = GM_CC["@mozilla.org/observer-service;1"].getService(GM_CI.nsIObserverService);
  23.   this._cookies = GM_CC["@longfocus.com/gmanager/cookies;1"].getService(GM_CI.gmICookies);
  24. }
  25.  
  26. gmServiceGmail.prototype = {
  27.   _loggedIn: false,
  28.   _status: GM_NOTIFY_ACCOUNT_STATE_LOGGED_OUT,
  29.   _inboxUnread: 0,
  30.   _savedDrafts: 0,
  31.   _spamUnread: 0,
  32.   _labels: null,
  33.   _snippets: null,
  34.   _spaceUsed: "0 MB",
  35.   _percentUsed: "0%",
  36.   _totalSpace: "0 MB",
  37.   _timer: null,
  38.   _interval: 0,
  39.   _connectionPhase: 0,
  40.   _isHosted: false,
  41.   _isChecking: false,
  42.   _cookieData: null,
  43.   
  44.   get loggedIn() { return this._loggedIn; },
  45.   get status() { return this._status; },
  46.   get inboxUnread() { return this._inboxUnread; },
  47.   get savedDrafts() { return this._savedDrafts; },
  48.   get spamUnread() { return this._spamUnread; },
  49.   get spaceUsed() { return this._spaceUsed; },
  50.   get percentUsed() { return this._percentUsed; },
  51.   get totalSpace() { return this._totalSpace; },
  52.   
  53.   getLabels: function(aCount)
  54.   {
  55.     if (this._labels == null)
  56.       this._labels = new Array();
  57.     aCount.value = this._labels.length;
  58.     return this._labels;
  59.   },
  60.   
  61.   getSnippets: function(aCount)
  62.   {
  63.     if (this._snippets == null)
  64.       this._snippets = new Array();
  65.     aCount.value = this._snippets.length;
  66.     return this._snippets;
  67.   },
  68.   
  69.   login: function()
  70.   {
  71.     // Check for password
  72.     if (this.password == null || this.password == "")
  73.     {
  74.       this._setStatus(GM_NOTIFY_ACCOUNT_STATE_LOGGED_OUT_NO_PASSWORD);
  75.     }
  76.     else if (!this._isChecking)
  77.     {
  78.       var url = null;
  79.       var data = null;
  80.       
  81.       // Initialize login
  82.       this._loggedIn = false;
  83.       this._connectionPhase = 0;
  84.       this._setChecking(true);
  85.       
  86.       try {
  87.         this._timer = GM_CC["@mozilla.org/timer;1"].createInstance(GM_CI.nsITimer);
  88.         this._timer.initWithCallback(this, 30000, this._timer.TYPE_ONE_SHOT);
  89.       } catch(e) {}
  90.       
  91.       if (this.email.indexOf("@gmail.com") > -1 || this.email.indexOf("@googlemail.com") > -1 || this.email.indexOf("@") == -1)
  92.       {
  93.         this._isHosted = false;
  94.         
  95.         url = "https://www.google.com/accounts/ServiceLoginAuth";
  96.         data = "ltmpl=wsad<mplcache=2" +
  97.                "&continue=https://mail.google.com/mail/?&service=mail&rm=false<mpl=wsad" +
  98.                "&Email=" + encodeURIComponent(this.email) +
  99.                "&Passwd=" + encodeURIComponent(this.password) +
  100.                "&null=Sign+in";
  101.       }
  102.       else
  103.       {
  104.         this._isHosted = true;
  105.         
  106.         var username = this.email.split("@")[0];
  107.         var domain = this.email.split("@")[1];
  108.         
  109.         url = "https://www.google.com/a/" + domain + "/LoginAction";
  110.         data = "at=null&continue=https://mail.google.com/hosted/" + domain + "/&service=mail" +
  111.                "&userName=" + encodeURIComponent(username) +
  112.                "&password=" + encodeURIComponent(this.password);
  113.       }
  114.       
  115.       // Send request
  116.       this._serverRequest(url, data);
  117.     }
  118.   },
  119.   
  120.   logout: function()
  121.   {
  122.     this._defaults();
  123.     this._setChecking(false);
  124.     this._setStatus(GM_NOTIFY_ACCOUNT_STATE_LOGGED_OUT);
  125.     
  126.     if (this._timer) {
  127.       this._timer.cancel();
  128.       this._timer = null;
  129.     }
  130.   },
  131.   
  132.   check: function()
  133.   {
  134.     if (!this._isChecking)
  135.     {
  136.       // Initialize login
  137.       this._connectionPhase = 1;
  138.       this._setChecking(true);
  139.       
  140.       try {
  141.         this._timer = GM_CC["@mozilla.org/timer;1"].createInstance(GM_CI.nsITimer);
  142.         this._timer.initWithCallback(this, 30000, this._timer.TYPE_ONE_SHOT);
  143.       } catch(e) {}
  144.       
  145.       // Send request
  146.       this._serverRequest(this._getPhaseURL(), null);
  147.     }
  148.   },
  149.   
  150.   setTimer: function(aInterval)
  151.   {
  152.     if (this._timer) {
  153.       this._timer.cancel();
  154.       this._timer = null;
  155.     }
  156.     
  157.     if (aInterval > 0) {
  158.       try {
  159.         this._interval = aInterval;
  160.         this._timer = GM_CC["@mozilla.org/timer;1"].createInstance(GM_CI.nsITimer);
  161.         this._timer.initWithCallback(this, aInterval * 60000, this._timer.TYPE_ONE_SHOT);
  162.       } catch(e) {}
  163.     }
  164.   },
  165.   
  166.   notify: function(aTimer)
  167.   {
  168.     if (this._isChecking)
  169.     {
  170.       // Sets the timeout error
  171.       this._setChecking(false);
  172.       this._setStatus(GM_NOTIFY_ACCOUNT_STATE_ERROR_TIMEOUT);
  173.       
  174.       // Retry login in 30 seconds
  175.       this.setTimer(0.3);
  176.     }
  177.     else
  178.       this.check();
  179.   },
  180.   
  181.   resetUnread: function()
  182.   {
  183.     // Reset account info
  184.     this._inboxUnread = 0;
  185.     this._savedDrafts = 0;
  186.     this._spamUnread = 0;
  187.     this._snippets = null;
  188.     
  189.     // Reset timer so info doesn't update
  190.     this.setTimer(this._interval);
  191.   },
  192.   
  193.   _setStatus: function(aStatus)
  194.   {
  195.     this._status = aStatus;
  196.     this._observer.notifyObservers(null, this.email, aStatus);
  197.   },
  198.   
  199.   _setChecking: function(aChecking)
  200.   {
  201.     if (aChecking)
  202.     {
  203.       // Load Google cookies
  204.       this._cookies.loadSession("google.com");
  205.       
  206.       // Set status connecting
  207.       this._setStatus(GM_NOTIFY_ACCOUNT_STATE_CONNECTING);
  208.       
  209.       try {
  210.         // Adds the HTTP observers
  211.         this._observer.addObserver(this, "http-on-modify-request", false);
  212.         this._observer.addObserver(this, "http-on-examine-response", false);
  213.       } catch(e) {}
  214.     }
  215.     else
  216.     {
  217.       // Restore Google cookies
  218.       this._cookies.restoreSession("google.com");
  219.       
  220.       try {
  221.         // Removes the HTTP observers
  222.         this._observer.removeObserver(this, "http-on-modify-request");
  223.         this._observer.removeObserver(this, "http-on-examine-response");
  224.       } catch(e) {}
  225.     }
  226.     
  227.     // Set checking
  228.     this._isChecking = aChecking;
  229.   },
  230.   
  231.   _defaults: function()
  232.   {
  233.     // Reset password
  234.     if (!this.remember)
  235.       this.password = null;
  236.     
  237.     // Account details
  238.     this._loggedIn = false;
  239.     this._inboxUnread = 0;
  240.     this._savedDrafts = 0;
  241.     this._spamUnread = 0;
  242.     this._labels = null;
  243.     this._snippets = null;
  244.     this._spaceUsed = "0 MB";
  245.     this._percentUsed = "0%";
  246.     this._totalSpace = "0 MB";
  247.     
  248.     // Login help
  249.     this._timer = null;
  250.     this._interval = 0;
  251.     this._connectionPhase = 0;
  252.     this._isHosted = false;
  253.     this._isChecking = false;
  254.     this._cookieData = null;
  255.   },
  256.   
  257.   _error: function(aError)
  258.   {
  259.     this._defaults();
  260.     this._setChecking(false);
  261.     this._setStatus(aError);
  262.   },
  263.   
  264.   _getPhaseURL: function()
  265.   {
  266.     var url = null;
  267.     var search = (this._connectionPhase == 1 ? "" : "?search=inbox&view=tl&start=0&init=1")
  268.     
  269.     if (!this._isHosted)
  270.       url = "https://mail.google.com/mail/";
  271.     else
  272.     {
  273.       var domain = this.email.split("@")[1];
  274.       url = "https://mail.google.com/hosted/" + domain + "/";
  275.     }
  276.     
  277.     return (url + search);
  278.   },
  279.   
  280.   _serverRequest: function(aURL, aData)
  281.   {
  282.     var ioService = GM_CC["@mozilla.org/network/io-service;1"].createInstance(GM_CI.nsIIOService);
  283.     var uri = ioService.newURI(aURL, "gmanager/" + this.email + "/", null);
  284.     var channel = ioService.newChannelFromURI(uri);
  285.     
  286.     if (aData != null)
  287.     {
  288.       var uploadStream = GM_CC["@mozilla.org/io/string-input-stream;1"].createInstance(GM_CI.nsIStringInputStream);
  289.       uploadStream.setData(aData, aData.length);
  290.       
  291.       var uploadChannel = channel.QueryInterface(GM_CI.nsIUploadChannel);
  292.       uploadChannel.setUploadStream(uploadStream, "application/x-www-form-urlencoded", -1);
  293.       
  294.       channel.QueryInterface(GM_CI.nsIHttpChannel).requestMethod = "POST";
  295.     }
  296.     
  297.     channel.asyncOpen(new this.observer(this), null);
  298.   },
  299.   
  300.   observe: function(aSubject, aTopic, aData)
  301.   {
  302.     var httpChannel = aSubject.QueryInterface(GM_CI.nsIHttpChannel);
  303.     var isGood = (httpChannel.originalURI.originCharset.indexOf("gmanager/" + this.email + "/") > -1);
  304.     
  305.     if (aTopic == "http-on-modify-request" && isGood)
  306.     {
  307.       var cookie = (this._cookieData != null ? this._cookieData : "");
  308.       
  309.       // Clears the Cookie data
  310.       httpChannel.setRequestHeader("Cookie", "", false);
  311.       
  312.       for (var name in this._cookieData)
  313.         httpChannel.setRequestHeader("Cookie", this._cookieData[name], true);
  314.     }
  315.     else if (aTopic == "http-on-examine-response" && isGood)
  316.     {
  317.       var cookies = null;
  318.       
  319.       try {
  320.         cookies = httpChannel.getResponseHeader("Set-Cookie").split("\n");
  321.       } catch(e) {}
  322.       
  323.       if (cookies != null)
  324.       {
  325.         if (this._cookieData == null)
  326.           this._cookieData = new Object();
  327.         
  328.         for (var i = 0; i < cookies.length; i++)
  329.         {
  330.           var cookieValue = (cookies[i].split(";"))[0];
  331.           var cookieName = (cookieValue.split("="))[0];
  332.           
  333.           if (cookieName.indexOf("ID") > -1 || cookieName.indexOf("GX") > -1)
  334.             this._cookieData[cookieName] = cookieValue;
  335.         }
  336.       }
  337.       
  338.       // Clears the Set-Cookie header (Firefox 2.0)
  339.       httpChannel.setResponseHeader("Set-Cookie", "", false);
  340.     }
  341.   },
  342.   
  343.   callback: function(aData, aRequest)
  344.   {
  345.     // Get the http channel containing our data
  346.     var httpChannel = aRequest.QueryInterface(GM_CI.nsIHttpChannel);
  347.     
  348.     var network = true;
  349.     var status = null;
  350.     var statusText = null;
  351.     
  352.     try {
  353.       // Get HTTP response
  354.       status = httpChannel.responseStatus;
  355.       statusText = httpChannel.responseStatusText;
  356.     } catch (e) {
  357.       network = false;
  358.     }
  359.     
  360.     /*
  361.     this._console.logStringMessage(this.email);
  362.     this._console.logStringMessage(aData);
  363.     this._console.logStringMessage(status + " - " + statusText)
  364.     this._console.logStringMessage(network);
  365.     this._console.logStringMessage("---");
  366.     */
  367.     
  368.     if (!network) // Network was disconnected
  369.     {
  370.       // Sets the network error
  371.       this._setChecking(false);
  372.       this._setStatus(GM_NOTIFY_ACCOUNT_STATE_ERROR_NETWORK);
  373.       
  374.       // Retry login
  375.       this.setTimer(0.3);
  376.       
  377.       return;
  378.     }
  379.     else if (status == null || status != 200) // Status wasn't good
  380.     {
  381.       // Check status
  382.       if (status >= 500)
  383.         this.check(); // Server error, try again
  384.       else
  385.         this._error(GM_NOTIFY_ACCOUNT_STATE_ERROR_SERVER);
  386.       
  387.       return;
  388.     }
  389.     else if (aData.indexOf("Welcome to Gmail") > -1)
  390.     {
  391.       // Sets the account error
  392.       if (aData.indexOf("Username and password do not match") > -1)
  393.       {
  394.         // Reset the password
  395.         this._password = null;
  396.         
  397.         // Sets error for incorrect password
  398.         this._error(GM_NOTIFY_ACCOUNT_STATE_ERROR_PASSWORD);
  399.       }
  400.       else // Sets error for captcha required
  401.         this._error(GM_NOTIFY_ACCOUNT_STATE_ERROR_CAPTCHA);
  402.       
  403.       return;
  404.     }
  405.     else if (aData.indexOf("Server error") > -1)
  406.     {
  407.       // Reset the password
  408.       this._password = null;
  409.       
  410.       // Sets error for incorrect password
  411.       this._error(GM_NOTIFY_ACCOUNT_STATE_ERROR_PASSWORD);
  412.       
  413.       return;
  414.     }
  415.     
  416.     // Eveything looks good =)
  417.     switch (++this._connectionPhase)
  418.     {
  419.       case 1:
  420.       case 2:
  421.       {
  422.         // Send request
  423.         this._serverRequest(this._getPhaseURL(), null);
  424.         break;
  425.       }
  426.       case 3:
  427.       {
  428.         var newMail = false;
  429.         
  430.         // Resets the timer
  431.         this.setTimer(this._interval);
  432.         
  433.         try {
  434.           // Quota ["qu","68 MB","2610 MB","3%","#006633"]
  435.           var myMatches = aData.match(/\["qu",.*\]/);
  436.           var myValues = eval(myMatches[0]);
  437.           
  438.           this._spaceUsed = myValues[1] + (myValues[1].indexOf("MB") == -1 ? " MB" : "");
  439.           this._totalSpace = myValues[2] + (myValues[2].indexOf("MB") == -1 ? " MB" : "");
  440.           this._percentUsed = myValues[3] + (myValues[3].indexOf("%") == -1 ? "%" : "");
  441.         } catch(e) {}
  442.         
  443.         try {
  444.           //  method 1 = ["ds",1,0,0,1,0,0,0] inbox, drafts, spam
  445.           //  method 2 = ["ds",[["inbox",0],["drafts",0],["spam",0]]]
  446.           var myMatches = null;
  447.           var isNew = aData.match(/\["ds",\[\[/);
  448.           
  449.           if (isNew)
  450.             myMatches = aData.match(/\["ds",.*\n(.*]\n)+\]/);
  451.           else
  452.             myMatches = aData.match(/\["ds",.*\]/);
  453.           
  454.           var myValues = eval(myMatches[0]);
  455.           var tempInbox = (isNew) ? myValues[1][0][1] : myValues[1];
  456.           
  457.           // Checks for new mail
  458.           newMail = (tempInbox > this._inboxUnread);
  459.           
  460.           this._inboxUnread = tempInbox;
  461.           this._savedDrafts = (isNew) ? myValues[1][1][1] : myValues[4];
  462.           this._spamUnread = (isNew) ? myValues[1][2][1] : myValues[6];
  463.         } catch(e) {}
  464.         
  465.         try {
  466.           // Labels ["ct",[["label1",0],["label2",0]]]
  467.           var myMatches = aData.match(/\["ct",.*\n(.*]\n)+\]/gm);
  468.           var myValues = eval(eval(myMatches[0])[1]);
  469.           
  470.           // Clear the labels
  471.           this._labels = new Array();
  472.           
  473.           for (var i = 0; i < myValues.length; i++)
  474.           {
  475.             var label = new gmServiceGmailLabel();
  476.             
  477.             // Set label
  478.             label.name = myValues[i][0];
  479.             label.unread = myValues[i][1];
  480.             
  481.             // Add the label
  482.             this._labels.push(label);
  483.           }
  484.         } catch(e) {}
  485.         
  486.         try {
  487.           // New Mail Snippets
  488.           var myMatches = aData.match(/\["t",.*\n(.*]\n)+\]/gm);
  489.           var myValues = eval(myMatches[0]);
  490.           
  491.           // Clear the snippets
  492.           this._snippets = new Array();
  493.           
  494.           for (var j = 0; j < myMatches.length; j++)
  495.           {
  496.             var myValues = eval(myMatches[j]);
  497.             
  498.             for (var i = 1; i < myValues.length; i++)
  499.             {
  500.               var mySnippet = myValues[i];
  501.               var snippet = new gmServiceGmailSnippet();
  502.               
  503.               if (mySnippet[1] == "1")
  504.               {
  505.                 // Set snippet
  506.                 snippet.id = mySnippet[0];
  507.                 snippet.time = this._stripHtml(mySnippet[3]);
  508.                 snippet.sender = this._stripHtml(mySnippet[4]);
  509.                 snippet.subject = this._replaceHtmlCodes(this._stripHtml(mySnippet[6]));
  510.                 snippet.msg = this._replaceHtmlCodes(this._stripHtml(mySnippet[7]));
  511.                 snippet.labels = mySnippet[8];
  512.                 snippet.files = mySnippet[9]; // Separated by commas
  513.                 snippet.date = mySnippet[12];
  514.                 
  515.                 // Add the snippet
  516.                 this._snippets.push(snippet);
  517.               }
  518.             }
  519.           }
  520.         } catch(e) {}
  521.         
  522.         this._loggedIn = true;
  523.         this._setChecking(false);
  524.         
  525.         if (newMail)
  526.           this._setStatus(GM_NOTIFY_ACCOUNT_STATE_LOGGED_IN_NEW_MAIL);
  527.         else
  528.           this._setStatus(GM_NOTIFY_ACCOUNT_STATE_LOGGED_IN_NO_MAIL);
  529.         
  530.         break;
  531.       }
  532.     }
  533.   },
  534.   
  535.   _stripHtml: function(aString)
  536.   {
  537.     return aString.replace(/(<([^>]+)>)/ig, "");
  538.   },
  539.   
  540.   _replaceHtmlCodes: function(aString)
  541.   {
  542.     var htmlCodes = new Array(
  543.       [">", ">"], ["<", "<"], ["'", "'"], [""", "\""],
  544.       ["&", "&"], ["˜", "~"], ["™", "?"], ["©", "?"],
  545.       ["®", "?"], ["…", ""] );
  546.     
  547.     for (var i = 0; i < htmlCodes.length; i++) {
  548.       var re = new RegExp(htmlCodes[i][0], "g");
  549.       aString = aString.replace(re, htmlCodes[i][1]);
  550.     }
  551.     
  552.     return aString;
  553.   },
  554.   
  555.   observer: function(aThis)
  556.   {
  557.     return ({
  558.       _myData: "",
  559.       
  560.       onStartRequest: function(aRequest, aContext) {
  561.         this._myData = "";
  562.       },
  563.       
  564.       onStopRequest: function(aRequest, aContext, aStatus) {
  565.         aThis.callback(this._myData, aRequest);
  566.       },
  567.       
  568.       onDataAvailable: function(aRequest, aContext, aStream, aSourceOffset, aLength) {
  569.         var scriptableInputStream = GM_CC["@mozilla.org/scriptableinputstream;1"].createInstance(GM_CI.nsIScriptableInputStream);
  570.         scriptableInputStream.init(aStream);
  571.         this._myData += scriptableInputStream.read(aLength);
  572.       }});
  573.   },
  574.   
  575.   QueryInterface: function(iid)
  576.   {
  577.     if (!iid.equals(GM_CI.gmIServiceGmail) && 
  578.         !iid.equals(GM_CI.nsISupports))
  579.       throw Components.results.NS_ERROR_NO_INTERFACE;
  580.     return this;
  581.   }
  582. }
  583.  
  584. function gmServiceGmailLabel() {}
  585. gmServiceGmailLabel.prototype = {
  586.   _name: null,
  587.   _unread: null,
  588.   
  589.   get name() { return this._name; },
  590.   get unread() { return this._unread; },
  591.   
  592.   set name(aName) { this._name = aName; },
  593.   set unread(aUnread) { this._unread = aUnread; }
  594. }
  595.  
  596. function gmServiceGmailSnippet() {}
  597. gmServiceGmailSnippet.prototype = {
  598.   _id: null,
  599.   _time: null,
  600.   _sender: null,
  601.   _subject: null,
  602.   _msg: null,
  603.   _labels: null,
  604.   _files: null,
  605.   _date: null,
  606.   
  607.   get id() { return this._id; },
  608.   get time() { return this._time; },
  609.   get sender() { return this._sender; },
  610.   get subject() { return this._subject; },
  611.   get msg() { return this._msg; },
  612.   get labels() { return this._labels; },
  613.   get files() { return this._files; },
  614.   get date() { return this._date; },
  615.   
  616.   set id(aId) { this._id = aId; },
  617.   set time(aTime) { this._time = aTime; },
  618.   set sender(aSender) { this._sender = aSender; },
  619.   set subject(aSubject) { this._subject = aSubject; },
  620.   set msg(aMsg) { this._msg = aMsg; },
  621.   set labels(aLabels) { this._labels = aLabels; },
  622.   set files(aFiles) { this._files = aFiles; },
  623.   set date(aDate) { this._date = aDate; }
  624. }
  625.  
  626. var myModule = {
  627.   firstTime: true,
  628.   
  629.   myCID: Components.ID("{b07df9d0-f7dd-11da-974d-0800200c9a66}"),
  630.   myDesc: "Service for Gmail accounts",
  631.   myProgID: "@longfocus.com/gmanager/service/gmail;1",
  632.   myFactory: {
  633.     createInstance: function (outer, iid) {
  634.       if (outer != null)
  635.         throw Components.results.NS_ERROR_NO_AGGREGATION;
  636.       
  637.       return (new gmServiceGmail()).QueryInterface(iid);
  638.     }
  639.   },
  640.  
  641.   registerSelf: function (compMgr, fileSpec, location, type)
  642.   {
  643.     if (this.firstTime) {
  644.       this.firstTime = false;
  645.       throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  646.     }
  647.     
  648.     compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  649.     compMgr.registerFactoryLocation(this.myCID, this.myDesc, this.myProgID, fileSpec, location, type);
  650.   },
  651.  
  652.   getClassObject: function (compMgr, cid, iid)
  653.   {
  654.     if (!cid.equals(this.myCID))
  655.       throw Components.results.NS_ERROR_NO_INTERFACE;
  656.     
  657.     if (!iid.equals(Components.interfaces.nsIFactory))
  658.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  659.     
  660.     return this.myFactory;
  661.   },
  662.   
  663.   canUnload: function(compMgr) { return true; }
  664. };
  665.  
  666. function NSGetModule(compMgr, fileSpec) { return myModule; }